package ar.edu.uba.fi.jmit.aspecter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtClass;
import ar.edu.uba.fi.jmit.aspecter.core.injector.Injector;
import ar.edu.uba.fi.jmit.aspecter.testUtils.ExceptionInjection;
import ar.edu.uba.fi.jmit.aspecter.testUtils.ExceptionInjectionAfter;
import ar.edu.uba.fi.jmit.aspecter.testUtils.TestUtils;

public class MethodInjectorTestCase extends AspecterTestCase {

	/**
	 * Inject on "getX()" method the code: "throw new RuntimeException(\"injected!\");"
	 */
	public void testInjectOnGetXMethod() {
		Injector injector = new ExceptionInjection("getX", int.class);
		try {
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest1", getNextClassName());
			injector.inject(ctClazz);
			Class<?> clazz = ctClazz.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("getY").invoke(point);
			try {
				clazz.getMethod("getX").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInjectMultipleInject() {
		Injector injectorX = new ExceptionInjection("getX", int.class);
		Injector injectorY = new ExceptionInjection("getY", int.class);
		try {
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest1", getNextClassName());
			injectorX.inject(ctClazz);
			injectorY.inject(ctClazz);
			Class<?> clazz = ctClazz.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY", int.class).invoke(point, 1);
			clazz.getMethod("setX", int.class).invoke(point, 2);
			try {
				clazz.getMethod("getX").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
			try {
				clazz.getMethod("getY").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInsertAfterCode() {
		try {
//			CtClass ctClazz = TestUtils.getPointCopyClassForTest();
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.Point", getNextClassName());
			Injector injector = new ExceptionInjectionAfter("setX", void.class);
			injector.inject(ctClazz);
			Class<?> clazz = ctClazz.toClass();
			Method setXMethod = TestUtils.getMethod(clazz, "setX", new Class<?>[]{int.class});
			assertNotNull(setXMethod);
			Method setYMethod = TestUtils.getMethod(clazz, "setY", new Class<?>[]{int.class});
			assertNotNull(setYMethod);
			Object point = clazz.newInstance();
			setYMethod.invoke(point, 1);
			try {
				setXMethod.invoke(point, 2);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
			//check if the value was changed (the exception should be thrown AFTER setting the value)
			Method getXMethod = TestUtils.getMethod(clazz, "getX", new Class<?>[]{});
			assertEquals(2, getXMethod.invoke(point));
			
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testHaveToInjectInjectAll() {
		try {
//			CtClass ctClazz = TestUtils.getPointCopyClassForTest();
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.Point", getNextClassName());
			Injector injector = new ExceptionInjection("someMethod", String.class);
			injector.inject(ctClazz);
			Class<?> clazz = ctClazz.toClass();
			Object point = clazz.newInstance();
			Method someMethodNoParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{});
			assertNotNull(someMethodNoParams);
			try {
				someMethodNoParams.invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
			Method someMethodWithParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{String.class});
			assertNotNull(someMethodWithParams);
			try {
				someMethodWithParams.invoke(point, "some string");
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testHaveToInjectCheckParameters() {
		try {
//			CtClass ctClazz = TestUtils.getPointCopyClassForTest();
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.Point", getNextClassName());
			ExceptionInjection injector = new ExceptionInjection("someMethod", String.class);
			injector.addParameterTypes(new Class[]{String.class});
			injector.inject(ctClazz);
			Class<?> clazz = ctClazz.toClass();
			Object point = clazz.newInstance();
			Method someMethodNoParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{});
			assertNotNull(someMethodNoParams);
			try {
				someMethodNoParams.invoke(point);
			}catch(InvocationTargetException e) {
				fail(e);
			}
			Method someMethodWithParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{String.class});
			assertNotNull(someMethodWithParams);
			try {
				someMethodWithParams.invoke(point, "some string");
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testHaveToInjectCheckParameters2() {
		try {
//			CtClass ctClazz = TestUtils.getPointCopyClassForTest();
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.Point", getNextClassName());
			ExceptionInjection injector = new ExceptionInjection("someMethod", String.class);
			injector.addParameterTypes(new Class[]{});
			injector.inject(ctClazz);
			Class<?> clazz = ctClazz.toClass();
			Object point = clazz.newInstance();
			Method someMethodNoParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{});
			assertNotNull(someMethodNoParams);
			try {
				someMethodNoParams.invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("injected!", e.getTargetException().getMessage());
			}
			Method someMethodWithParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{String.class});
			assertNotNull(someMethodWithParams);
			try {
				someMethodWithParams.invoke(point, "some string");
			}catch(InvocationTargetException e) {
				fail(e);
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testHaveToInjectCheckReturnType() {
		try {
//			CtClass ctClazz = TestUtils.getPointCopyClassForTest();
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.Point", getNextClassName());
			ExceptionInjection injector = new ExceptionInjection("someMethod", int.class);
			injector.addParameterTypes(new Class[]{});
			injector.inject(ctClazz);
			Class<?> clazz = ctClazz.toClass();
			Object point = clazz.newInstance();
			Method someMethodNoParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{});
			assertNotNull(someMethodNoParams);
			someMethodNoParams.invoke(point);
			Method someMethodWithParams = TestUtils.getMethod(clazz, "someMethod", new Class<?>[]{String.class});
			assertNotNull(someMethodWithParams);
			try {
				someMethodWithParams.invoke(point, "some string");
			}catch(InvocationTargetException e) {
				fail(e);
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
}

	